﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using Hive.Core.Common.Shell;
using Hive.Core.Model;
using Hive.Core.Model.Commands;

namespace Hive.Core.ConsoleApplication
{
    /// <summary>
    /// Represents the parsed Hive arguments.
    /// </summary>
    public class HiveArguments
    {
        private readonly Command command;
        private readonly Verbosity verbosity;

        private HiveArguments(Command command, Verbosity verbosity)
        {
            this.command = command;
            this.verbosity = verbosity;
        }

        /// <summary>
        /// Parses the command-line arguments.
        /// </summary>
        /// <param name="shell">The shell.</param>
        /// <param name="args">The command-line arguments.</param>
        /// <returns>The parsed arguments, or null if an error occurred.</returns>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="shell"/>
        /// or <paramref name="args"/> is null or contains null.</exception>
        public static HiveArguments Parse(IShell shell, string[] args)
        {
            if (shell == null)
                throw new ArgumentNullException("shell");
            if (args == null || Array.IndexOf(args, null) >= 0)
                throw new ArgumentNullException("args");

            var argumentIterator = new ArgumentIterator(args);

            try
            {
                HiveArguments hiveArguments = ParseCommand(shell, argumentIterator);
                if (hiveArguments != null)
                    return hiveArguments;
            }
            catch (Exception ex)
            {
                ShowErrorMessage(shell, string.Format("Invalid arguments.  {0}", ex));
            }

            ShowUsageSummary(shell);
            return null;
        }

        private static HiveArguments ParseCommand(IShell shell, ArgumentIterator argumentIterator)
        {
            if (!argumentIterator.Next())
            {
                ShowErrorMessage(shell, "Missing command name.");
                return null;
            }

            string commandName = argumentIterator.CurrentToLower;
            CommandParser commandParser = CreateCommandParser(commandName, shell);
            if (commandParser != null)
                return commandParser.Parse(argumentIterator, shell);

            return null;
        }

        private static CommandParser CreateCommandParser(string commandName, IShell shell)
        {
            switch (commandName)
            {
                case "create-hive":
                    return new CreateHiveCommandParser();

                case "add-source-repo":
                    return new AddSourceRepositoryMappingCommandParser();

                case "add-binary-repo":
                    return new AddBinaryRepositoryMappingCommandParser();

                case "get-source":
                    return new GetSourcePackageBranchCommandParser();

                case "get-binary":
                    return new GetBinaryPackageBranchCommandParser();

                case "build":
                    return new BuildPackageBranchCommandParser();

                case "refresh":
                    return new RefreshPackageBranchCommandParser();

                case "help":
                case "?":
                    return null;

                default:
                    ShowErrorMessage(shell, string.Format("Unrecognized command name '{0}'.", commandName));
                    return null;
            }
        }

        /// <summary>
        /// Gets the command to run.
        /// </summary>
        public Command Command
        {
            get { return command; }
        }

        /// <summary>
        /// Gets the verbosity setting.
        /// </summary>
        public Verbosity Verbosity
        {
            get { return verbosity; }
        }

        private static void ShowErrorMessage(IShell shell, string message)
        {
            shell.Echo(MessageSeverity.Error, "\n");
            shell.Echo(MessageSeverity.Error, message);
        }

        private static void ShowUsageSummary(IShell shell)
        {
            string usage = GetUsageText();
            shell.Echo(MessageSeverity.Important, "\n");
            shell.Echo(MessageSeverity.Important, usage);
        }

        private static string GetUsageText()
        {
            using (var reader = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Hive.Core.ConsoleApplication.HiveUsage.txt")))
                return reader.ReadToEnd();
        }

        private sealed class ArgumentIterator
        {
            private readonly string[] args;
            private int index;

            public ArgumentIterator(string[] args)
            {
                this.args = args;
                index = -1;
            }

            public int Remaining
            {
                get { return args.Length - index - 1; }
            }

            public string Current
            {
                get { return args[index]; }
            }

            public string CurrentToLower
            {
                get { return Current.ToLowerInvariant(); }
            }

            public bool CurrentIsSwitch
            {
                get { return Current.StartsWith("-"); }
            }

            public bool Next()
            {
                if (index + 1 == args.Length)
                    return false;

                index += 1;
                return true;
            }
        }

        private abstract class CommandParser
        {
            private Verbosity verbosity = Verbosity.Normal;

            public HiveArguments Parse(ArgumentIterator argumentIterator, IShell shell)
            {
                while (argumentIterator.Next())
                {
                    if (argumentIterator.CurrentIsSwitch)
                    {
                        if (!HandleSwitch(argumentIterator, shell))
                            return null;
                    }
                    else
                    {
                        if (!HandleDefaultArgument(argumentIterator, shell))
                            return null;
                    }
                }

                if (!CheckArguments(shell))
                    return null;

                Command command = CreateCommand();
                return new HiveArguments(command, verbosity);
            }

            protected virtual bool HandleSwitch(ArgumentIterator argumentIterator, IShell shell)
            {
                if (argumentIterator.CurrentToLower == "-quiet")
                {
                    verbosity = Verbosity.Quiet;
                    return true;
                }

                if (argumentIterator.CurrentToLower == "-verbose")
                {
                    verbosity = Verbosity.Detailed;
                    return true;
                }

                if (argumentIterator.CurrentToLower == "-debug")
                {
                    verbosity = Verbosity.Debug;
                    return true;
                }

                ShowErrorMessage(shell, string.Format("Invalid switch '{0}'.", argumentIterator.Current));
                return false;
            }

            protected virtual bool HandleDefaultArgument(ArgumentIterator argumentIterator, IShell shell)
            {
                ShowErrorMessage(shell, string.Format("Invalid argument '{0}'.", argumentIterator.Current));
                return false;
            }

            protected virtual bool CheckArguments(IShell shell)
            {
                return true;
            }

            protected abstract Command CreateCommand();
        }

        private class CreateHiveCommandParser : CommandParser
        {
            private DirectoryInfo hiveDirectory;

            protected override bool HandleDefaultArgument(ArgumentIterator argumentIterator, IShell shell)
            {
                if (hiveDirectory == null)
                {
                    hiveDirectory = new DirectoryInfo(argumentIterator.Current);
                    return true;
                }

                return base.HandleDefaultArgument(argumentIterator, shell);
            }

            protected override bool CheckArguments(IShell shell)
            {
                return hiveDirectory != null;
            }

            protected override Command CreateCommand()
            {
                return new CreateHiveCommand(hiveDirectory);
            }
        }

        private abstract class BaseHiveCommandParser : CommandParser
        {
            protected DirectoryInfo hiveDirectory;

            protected override bool HandleSwitch(ArgumentIterator argumentIterator, IShell shell)
            {
                if (argumentIterator.CurrentToLower == "-hive" && hiveDirectory == null
                    && argumentIterator.Remaining >= 1)
                {
                    argumentIterator.Next();
                    hiveDirectory = new DirectoryInfo(argumentIterator.Current);
                    return true;
                }

                return base.HandleSwitch(argumentIterator, shell);
            }
        }

        private class AddSourceRepositoryMappingCommandParser : BaseHiveCommandParser
        {
            private Regex packageBranchIdRegex;
            private string repositoryRootFormat;

            protected override bool HandleDefaultArgument(ArgumentIterator argumentIterator, IShell shell)
            {
                if (packageBranchIdRegex == null)
                {
                    packageBranchIdRegex = new Regex(argumentIterator.Current);
                    return true;
                }

                if (repositoryRootFormat == null)
                {
                    repositoryRootFormat = argumentIterator.Current;
                    return true;
                }

                return base.HandleDefaultArgument(argumentIterator, shell);
            }

            protected override bool CheckArguments(IShell shell)
            {
                return packageBranchIdRegex != null && repositoryRootFormat != null;
            }

            protected override Command CreateCommand()
            {
                return new AddSourceRepositoryMappingCommand(new SourceRepositoryConfiguration.Mapping(packageBranchIdRegex, repositoryRootFormat))
                {
                    HiveDirectory = hiveDirectory
                };
            }
        }

        private class AddBinaryRepositoryMappingCommandParser : BaseHiveCommandParser
        {
            private Regex artifactIdRegex;
            private string artifactPathFormat;

            protected override bool HandleDefaultArgument(ArgumentIterator argumentIterator, IShell shell)
            {
                if (artifactIdRegex == null)
                {
                    artifactIdRegex = new Regex(argumentIterator.Current);
                    return true;
                }

                if (artifactPathFormat == null)
                {
                    artifactPathFormat = argumentIterator.Current;
                    return true;
                }

                return base.HandleDefaultArgument(argumentIterator, shell);
            }

            protected override bool CheckArguments(IShell shell)
            {
                return artifactIdRegex != null && artifactPathFormat != null;
            }

            protected override Command CreateCommand()
            {
                return new AddBinaryRepositoryMappingCommand(new BinaryRepositoryConfiguration.Mapping(artifactIdRegex, artifactPathFormat))
                {
                    HiveDirectory = hiveDirectory
                };
            }
        }

        private abstract class BasePackageBranchCommandParser : BaseHiveCommandParser
        {
            protected string packageName;
            protected string branchName;

            protected override bool HandleDefaultArgument(ArgumentIterator argumentIterator, IShell shell)
            {
                if (packageName == null)
                {
                    packageName = argumentIterator.Current;
                    return true;
                }

                if (branchName == null)
                {
                    branchName = argumentIterator.Current;
                    return true;
                }

                return base.HandleDefaultArgument(argumentIterator, shell);
            }
        }

        private class GetSourcePackageBranchCommandParser : BasePackageBranchCommandParser
        {
            private bool build;

            protected override bool HandleSwitch(ArgumentIterator argumentIterator, IShell shell)
            {
                if (argumentIterator.CurrentToLower == "-build")
                {
                    build = true;
                    return true;
                }

                return base.HandleSwitch(argumentIterator, shell);
            }

            protected override Command CreateCommand()
            {
                return new GetSourcePackageBranchCommand()
                {
                    HiveDirectory = hiveDirectory,
                    PackageName = packageName,
                    BranchName = branchName,
                    Build = build
                };
            }
        }

        private class GetBinaryPackageBranchCommandParser : BasePackageBranchCommandParser
        {
            private string versionLabel;

            protected override bool HandleSwitch(ArgumentIterator argumentIterator, IShell shell)
            {
                if (argumentIterator.CurrentToLower == "-version"
                    && argumentIterator.Remaining >= 2)
                {
                    argumentIterator.Next();
                    versionLabel = argumentIterator.Current;
                    return true;
                }

                return base.HandleSwitch(argumentIterator, shell);
            }

            protected override Command CreateCommand()
            {
                return new GetBinaryPackageBranchCommand()
                {
                    HiveDirectory = hiveDirectory,
                    PackageName = packageName,
                    BranchName = branchName,
                    VersionLabel = versionLabel
                };
            }
        }

        private class RefreshPackageBranchCommandParser : BasePackageBranchCommandParser
        {
            private bool build;
            private RecursionMode recursionMode;

            protected override bool HandleSwitch(ArgumentIterator argumentIterator, IShell shell)
            {
                if (argumentIterator.CurrentToLower == "-build")
                {
                    build = true;
                    return true;
                }

                if (argumentIterator.CurrentToLower == "-dependencies" && recursionMode == RecursionMode.Self)
                {
                    recursionMode = RecursionMode.Dependencies;
                    return true;
                }

                if (argumentIterator.CurrentToLower == "-all" && recursionMode == RecursionMode.Self)
                {
                    recursionMode = RecursionMode.All;
                    return true;
                }

                return base.HandleSwitch(argumentIterator, shell);
            }

            protected override Command CreateCommand()
            {
                return new RefreshPackageBranchCommand()
                {
                    HiveDirectory = hiveDirectory,
                    PackageName = packageName,
                    BranchName = branchName,
                    Build = build,
                    RecursionMode = recursionMode
                };
            }
        }

        private class BuildPackageBranchCommandParser : BasePackageBranchCommandParser
        {
            protected override Command CreateCommand()
            {
                return new BuildPackageBranchCommand()
                {
                    HiveDirectory = hiveDirectory,
                    PackageName = packageName,
                    BranchName = branchName
                };
            }
        }
    }
}
